#pragma once

#include <mysql.h>

#include <li/callable_traits/callable_traits.hh>
#include <li/metamap/metamap.hh>
#include <li/sql/sql_common.hh>
#include <li/sql/symbols.hh>

#include <li/sql/internal/mysql_bind.hh>
#include <li/sql/internal/mysql_statement_data.hh>
#include <li/sql/mysql_async_wrapper.hh>
#include <li/sql/mysql_connection_data.hh>

namespace li {

/**
 * @brief The prepared statement result interface.
 *
 * @tparam B
 */
template <typename B> struct mysql_statement_result {

  mysql_statement_result& operator=(mysql_statement_result&) = delete;
  mysql_statement_result(const mysql_statement_result&) = delete;

  /**
   * @brief Destructor. Free the result if needed.
   */
  inline ~mysql_statement_result() { flush_results(); }

  inline void flush_results() {
    // if (result_allocated_)
    mysql_wrapper_.mysql_stmt_free_result(connection_->error_, data_.stmt_);
    // result_allocated_ = false;
  }

  // Read std::tuple and li::metamap.
  template <typename T> bool read(T&& output);

  template <typename T>
  bool read(T&& output, MYSQL_BIND* bind, unsigned long* real_lengths);

  template <typename F> void map(F map_callback);

  /**
   * @return the number of rows affected by the request.
   */
  long long int affected_rows();

  /**
   * @brief Return the last id generated by a insert comment.
   *
   * @return the last inserted id.
   */
  long long int last_insert_id();

  void next_row();

  // Internal methods.
  template <typename... A>
  void finalize_fetch(MYSQL_BIND* bind, unsigned long* real_lengths, metamap<A...>& o);
  template <typename... A>
  void finalize_fetch(MYSQL_BIND* bind, unsigned long* real_lengths, std::tuple<A...>& o);
  template <typename T> void fetch_column(MYSQL_BIND*, unsigned long, T&, int);

  void fetch_column(MYSQL_BIND* b, unsigned long real_length, std::string& v, int i);
  template <unsigned SIZE>
  void fetch_column(MYSQL_BIND& b, unsigned long real_length, sql_varchar<SIZE>& v, int i);
  template <typename T> int fetch(T&& o);

  B& mysql_wrapper_;
  mysql_statement_data& data_;
  std::shared_ptr<mysql_connection_data> connection_;
  bool result_allocated_ = false;
};

} // namespace li

#include <li/sql/mysql_statement_result.hpp>
